#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import io
import base64
import click
import traceback
import secrets
import string
from datetime import datetime
from functools import wraps
import subprocess

from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session, current_app
from flask_migrate import Migrate
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash
from flask_mail import Mail, Message

from config import Config
from models import db, Application, Supervisor, ApplicationStatus, UserType, AdminUser
import ldap_service
import pyotp
import qrcode
import textwrap

# --- App 初始化 ---
app = Flask(__name__)
app.config.from_object(Config)
app.secret_key = app.config.get('SECRET_KEY')
db.init_app(app)
mail = Mail(app) 
migrate = Migrate(app, db)

# --- Flask-Login 配置 ---
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'admin_login'
login_manager.login_message = '请先登录以访问此页面'
login_manager.login_message_category = 'error'

@login_manager.user_loader
def load_user(user_id):
    return db.session.get(AdminUser, int(user_id))

@app.context_processor
def inject_user_type():
    """将 UserType 枚举注入到所有Jinja2模板中"""
    return dict(UserType=UserType)
# app.py


# app.py

def send_supervisor_notification_email(supervisor_name, supervisor_email, student_name, student_id):
    """
    发送给【已注册】导师的学生申请审批提醒邮件。
    """
    try:
        msg = Message(
            subject=f"【算力平台】您有一名新学生待审批 - {student_name}",
            sender=current_app.config['MAIL_DEFAULT_SENDER'],
            recipients=[supervisor_email]
        )
        
        # 提供平台的登录链接
        login_link = "http://user.saids.hpc.gleamoe.com/"
        
        msg.body = f"""
                尊敬的 {supervisor_name} 导师：

                您好！

                学生 {student_name}（学号：{student_id}）提交了算力平台账户申请，需要您的审批确认。

                请您登录您的导师账号，在管理界面中对此申请进行审批。

                登录链接:
                {login_link}

                审批通过后，该学生的申请将提交给管理员进行最终审核。

                此致
                人工智能与数据科学学院算力中心
        """
        mail.send(msg)
        print(f"导师审批提醒邮件已发送至 {supervisor_email}")
    except Exception as e:
        print(f"发送导师审批提醒邮件失败: {e}")
def send_supervisor_invitation_email(supervisor_name, supervisor_email, student_name, student_id):
    """Sends an invitation email to an unregistered supervisor."""
    try:
        msg = Message(
            subject=f"【算力平台】邀请您注册平台以审批学生申请 - {student_name}",
            sender=current_app.config['MAIL_DEFAULT_SENDER'],
            recipients=[supervisor_email]
        )
        
        register_link = "http://user.saids.hpc.gleamoe.com/"
        
        msg.body = f"""
                尊敬的 {supervisor_name} 老师：

                您好！

                学生 {student_name}（学号：{student_id}）提交了算力平台账户申请，并指定您为导师。

                为了管理和审批您名下的学生，您需要先在本平台注册一个导师账户。

                请点击以下链接访问平台首页，并选择“导师注册”来创建您的账户。
                注册链接: {register_link}

                完成注册并登录后，您将可以在导师管理界面中看到并审批该学生的申请。

                此致
                人工智能与数据科学学院算力中心
        """
        mail.send(msg)
        print(f"导师邀请注册邮件已发送至 {supervisor_email}")
    except Exception as e:
        print(f"发送导师邀请注册邮件失败: {e}")

def send_credentials_email(to, username, password, group_name, totp_secret):
    """(最终美化版) 发送包含内嵌Logo和二维码的专业HTML凭据邮件给用户"""
    try:
        msg = Message(
            subject="【Saids.ustc】欢迎加入算力平台 - 您的账户已开通",
            sender=f"算力平台管理员 <{current_app.config['MAIL_DEFAULT_SENDER']}>",
            recipients=[to]
        )

        # --- 1. 准备内嵌图片资源 ---

        # a) 生成二维码图片数据
        provisioning_uri = pyotp.totp.TOTP(totp_secret).provisioning_uri(
            name=f"{username}@算力平台", issuer_name="SAIDS-USTC"
        )
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=8, border=4)
        qr.add_data(provisioning_uri)
        qr.make(fit=True)
        img = qr.make_image(fill_color="black", back_color="white")
        
        buf_qr = io.BytesIO()
        img.save(buf_qr, 'PNG')
        buf_qr.seek(0)
        qr_image_data = buf_qr.read()

        # b) 读取Logo图片数据
        logo_path = 'static/images/logo.png'
        with app.open_resource(logo_path) as fp:
            logo_image_data = fp.read()

        # --- 2. 构建美化的HTML邮件正文 ---
        login_url = "http://user.saids.hpc.gleamoe.com/"
        user_guide_url = "https://saids.hpc.gleamoe.com/"
        
        # 使用HTML和内联CSS来构建邮件模板
        msg.html = f"""
            <!DOCTYPE html>
            <html lang="zh-CN">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>账户开通通知</title>
            </head>
            <body style="margin: 0; padding: 0; background-color: #f4f7f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
                <table width="100%" border="0" cellpadding="0" cellspacing="0" style="background-color: #f4f7f6;">
                    <tr>
                        <td align="center" style="padding: 20px 0;">
                            <table width="600" border="0" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
                                <tr>
                                    <td align="center" style="padding: 30px 0; border-bottom: 1px solid #e8e8e8;">
                                        <img src="cid:logo_image" alt="平台Logo" width="80" style="display: block;">
                                        <h1 style="color: #0a2540; margin-top: 15px; font-size: 24px;">欢迎加入算力平台</h1>
                                    </td>
                                </tr>
                                <tr>
                                    <td style="padding: 30px 40px;">
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">同学您好！</p>
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">您的算力平台账户申请已被批准。请妥善保管以下登录信息，并务必阅读用户使用指南以完成首次登录。</p>
                                        
                                        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px;">账户信息</h3>
                                        <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px;">
                                            <tr><td width="120" style="color: #555;">登录节点:</td><td style="color: #111;">114.214.255.70</td></tr>
                                            <tr><td style="color: #555;">用户名:</td><td style="color: #111;">{username}</td></tr>
                                            <tr><td style="color: #555;">临时密码:</td><td style="color: #d9534f; font-weight: bold;">{password}<i>(也是Wed网页登录密码)</i></td></tr>
                                            <tr><td style="color: #555;">所属课题组:</td><td style="color: #111;">{group_name}</td></tr>
                                        </table>

                                        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px;">首次使用指南 (必读)</h3>
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">我们为您准备了详细的首次登录和使用教程，请点击下方按钮查看：</p>
                                        <table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding: 15px 0;">
                                            <a href="{login_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px; margin: 5px;">平台登录</a>
                                            <a href="{user_guide_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;">查看用户指南</a>
                                        </td></tr></table>

                                        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px;">Google Authenticator 设置</h3>
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">请使用手机验证器App扫描下方二维码。如果无法扫描，也可手动输入密钥。</p>
                                        <div style="text-align: center; padding: 20px 0;">
                                            <img src="cid:qrcode_image" alt="2FA QR Code">
                                        </div>
                                        <p style="text-align: center; font-size: 14px; color: #555;">密钥: <code style="background-color: #e8e8e8; padding: 3px 6px; border-radius: 3px;">{totp_secret}</code></p>
                                        
                                        <h3 style="color: #0a2540; margin-top: 0; font-size: 18px;">安全提示</h3>
                                        <p style="color: #555; font-size: 15px; line-height: 1.6;">
                                            - <b>修改临时密码</b>：首次通过SSH登录服务器后，请立即运行 <code>passwd</code> 命令修改您的临时密码。<br><br>
                                            - <b>保护2FA密钥</b>：密钥一旦泄露，请立即通过SSH登录服务器，运行 <code>google-authenticator</code> 命令作废旧密钥并重新生成。
                                        </p>                                    
                                    </td>
                                </tr>
                                <tr>
                                    <td style="padding: 30px 40px; border-top: 1px solid #e8e8e8; text-align: center; color: #999; font-size: 12px;">
                                        <p>此邮件为系统自动发送，请勿回复。</p>
                                        <p>人工智能与数据科学学院算力中心</p>
                                    </td>
                                </tr>
                            </table>
                        </td>
                    </tr>
                </table>
            </body>
            </html>
        """

        # --- 3. 将图片作为内嵌资源附加到邮件中 ---
        # a) 附加Logo
        msg.attach(
            filename='logo.png', content_type='image/png', data=logo_image_data,
            disposition='inline', headers={'Content-ID': '<logo_image>'}
        )
        # b) 附加二维码
        msg.attach(
            filename='qrcode.png', content_type='image/png', data=qr_image_data,
            disposition='inline', headers={'Content-ID': '<qrcode_image>'}
        )
        
        mail.send(msg)
        print(f"带二维码和用户指南的凭据邮件已成功发送至 {to}")
        
    except Exception as e:
        print(f"发送凭据邮件失败: {e}")
        traceback.print_exc()

def send_supervisor_credentials_email(to, username, password, group_name, totp_secret, web_password):
    """(最终美化版) 发送给新批准导师的、包含两种密码说明的专业HTML凭据邮件"""
    try:
        msg = Message(
            subject="【Saids.ustc】您的算力平台导师账户已开通",
            sender=f"算力平台管理员 <{current_app.config['MAIL_DEFAULT_SENDER']}>",
            recipients=[to]
        )

        # --- 1. 准备内嵌图片资源 (与学生邮件逻辑相同) ---
        # a) 生成二维码
        provisioning_uri = pyotp.totp.TOTP(totp_secret).provisioning_uri(
            name=f"{username}@算力平台", issuer_name="SAIDS-USTC"
        )
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=8, border=4)
        qr.add_data(provisioning_uri)
        qr.make(fit=True)
        img = qr.make_image(fill_color="black", back_color="white")
        
        buf_qr = io.BytesIO()
        img.save(buf_qr, 'PNG')
        buf_qr.seek(0)
        qr_image_data = buf_qr.read()

        # b) 读取Logo
        logo_path = 'static/images/logo.png'
        with app.open_resource(logo_path) as fp:
            logo_image_data = fp.read()

        # --- 2. 构建为导师定制的HTML邮件正文 ---
        
        login_url ="http://user.saids.hpc.gleamoe.com/"
        
        msg.html = f"""
            <!DOCTYPE html>
            <html lang="zh-CN">
            <head><meta charset="UTF-8"><title>导师账户开通通知</title></head>
            <body style="margin: 0; padding: 0; background-color: #f4f7f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
                <table width="100%" border="0" cellpadding="0" cellspacing="0" style="background-color: #f4f7f6;">
                    <tr>
                        <td align="center" style="padding: 20px 0;">
                            <table width="600" border="0" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
                                <tr>
                                    <td align="center" style="padding: 30px 0; border-bottom: 1px solid #e8e8e8;">
                                        <img src="cid:logo_image" alt="平台Logo" width="80" style="display: block;">
                                        <h1 style="color: #0a2540; margin-top: 15px; font-size: 24px;">您的导师账户已开通</h1>
                                    </td>
                                </tr>
                                <tr>
                                    <td style="padding: 30px 40px;">
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">尊敬的 {group_name} 导师, 您好！</p>
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">您的算力平台导师账户申请已被批准。您拥有两套独立的凭据，请注意区分其不同用途：</p>
                                        
                                        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #134790; padding-left: 10px;">1. Web平台管理凭据</h3>
                                        <p style="color: #555; font-size: 15px; line-height: 1.6;">用于登录Web管理界面，审批和管理您名下的学生。</p>
                                        <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
                                            <tr><td width="80" style="color: #555;">用户名:</td><td style="color: #111;">{username}</td></tr>
                                            <tr><td style="color: #555;">密  码:</td><td style="color: #111;">{web_password} <i>(您在注册时设置的密码)</i></td></tr>
                                        </table>
                                        <table width="100%" border="0" cellpadding="0" cellspacing="0"><tr><td align="center" style="padding: 20px 0;">
                                            <a href="{login_url}" target="_blank" style="background-color: #134790; color: #ffffff; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;">登录导师管理界面</a>
                                        </td></tr></table>

                                        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px; border-left: 4px solid #10b981; padding-left: 10px;">2. 服务器SSH登录凭据</h3>
                                        <p style="color: #555; font-size: 15px; line-height: 1.6;">用于您自己登录Linux服务器以使用算力资源</p>
                                        <table width="100%" border="0" cellpadding="10" cellspacing="0" style="background-color: #f8fafb; border: 1px solid #e8e8e8; border-radius: 5px; font-family: monospace; font-size: 14px; margin-top:10px;">
                                            <tr><td width="80" style="color: #555;">登录节点:</td><td style="color: #111;">114.214.255.70</td></tr>
                                            <tr><td style="color: #555;">用户名:</td><td style="color: #111;">{username}</td></tr>
                                            <tr><td style="color: #555;">临时密码:</td><td style="color: #d9534f; font-weight: bold;">{password}</td></tr>
                                        </table>

                                        <h3 style="color: #0a2540; margin-top: 25px; font-size: 18px;">Google Authenticator 设置 (用于SSH)</h3>
                                        <p style="color: #333; font-size: 16px; line-height: 1.6;">请扫描下方二维码以配置您的SSH两步验证。</p>
                                        <div style="text-align: center; padding: 20px 0;">
                                            <img src="cid:qrcode_image" alt="2FA QR Code">
                                        </div>
                                        <p style="text-align: center; font-size: 14px; color: #555;">密钥: <code style="background-color: #e8e8e8; padding: 3px 6px; border-radius: 3px;">{totp_secret}</code></p>
                                        
                                        <h3 style="color: #0a2540; margin-top: 0; font-size: 18px;">安全提示</h3>
                                        <p style="color: #555; font-size: 15px; line-height: 1.6;">
                                            - <b>修改临时密码</b>：首次通过SSH登录服务器后，请立即运行 <code>passwd</code> 命令修改您的临时密码。<br><br>
                                            - <b>保护2FA密钥</b>：密钥一旦泄露，请立即通过SSH登录服务器，运行 <code>google-authenticator</code> 命令作废旧密钥并重新生成。
                                        </p>

                                    </td>
                                </tr>
                                <tr>
                                    <td style="padding: 30px 40px; border-top: 1px solid #e8e8e8; text-align: center; color: #999; font-size: 12px;">
                                        <p>此邮件为系统自动发送，请勿回复。</p>
                                        <p>人工智能与数据科学学院算力中心</p>
                                    </td>
                                </tr>
                            </table>
                        </td>
                    </tr>
                </table>
            </body>
            </html>
        """

        # --- 3. 将图片作为内嵌资源附加到邮件中 ---
        msg.attach(
            filename='logo.png', content_type='image/png', data=logo_image_data,
            disposition='inline', headers={'Content-ID': '<logo_image>'}
        )
        msg.attach(
            filename='qrcode.png', content_type='image/png', data=qr_image_data,
            disposition='inline', headers={'Content-ID': '<qrcode_image>'}
        )
        
        mail.send(msg)
        print(f"导师凭据邮件已发送至 {to}")
        
    except Exception as e:
        print(f"发送导师凭据邮件失败: {e}")
        traceback.print_exc()


def _provision_ldap_account(username, full_name, email, supervisor_name, password, user_type):
    """(最终修正版) 核心辅助函数，封装所有LDAP账户开通和邮件发送的逻辑"""
    try:
        # 1. 为学生生成随机密码，导师则使用传入的密码
        if user_type == UserType.STUDENT:
            alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
            final_password = ''.join(secrets.choice(alphabet) for i in range(10))
        else: # 导师
            final_password = password

        totp_secret = pyotp.random_base32()
        
        # --- 修正开始 ---
        # 2. 准备LDAP信息，并确保获取正确的GID
        group_name = ldap_service.format_group_name(supervisor_name)
        home_dir = f"/home/{username}"
        next_uid_number = ldap_service.get_next_available_id('uid')

        # 核心修正：先检查组是否存在，以获取正确的gid_number
        existing_gid = ldap_service.get_gid_by_group_name(group_name)
        if existing_gid:
            # 如果组已存在，使用它已有的GID
            gid_for_user = existing_gid
            print(f"导师组 '{group_name}' 已存在，使用现有 GID: {gid_for_user}")
        else:
            # 如果组不存在，获取新GID并创建组
            new_gid = ldap_service.get_next_available_id('gid')
            ldap_service.create_group_if_not_exists(group_name, new_gid)
            gid_for_user = new_gid
            print(f"导师组 '{group_name}' 不存在，创建新组并使用 GID: {gid_for_user}")
        # --- 修正结束 ---

        # 3. LDAP操作：使用正确的 gid_for_user 创建用户
        ldap_service.create_user(
            username=username, password=final_password, full_name=full_name,
            email=email, totp_secret=totp_secret, uid_number=next_uid_number,
            gid_number=gid_for_user,  # <-- 使用修正后的GID
            home_directory=home_dir
        )
        ldap_service.add_user_to_group(username, group_name)
        
        # 4. 自动化配置2FA
        try:
            print(f"正在为用户 {username} 在共享 /home 目录配置 2FA...")
            command = ['sudo', '/home/user/setup_2fa.sh', username, totp_secret, group_name]
            subprocess.run(command, check=True)
            print("服务器端 2FA 配置成功。")
        except Exception as e:
            print(f"警告：自动配置服务器端 2FA 失败: {e}")

        # 5. 根据用户类型发送正确的邮件
        if user_type == UserType.SUPERVISOR:
            send_supervisor_credentials_email(
                to=email, username=username, password=final_password,
                group_name=supervisor_name, totp_secret=totp_secret,
                web_password=password
            )
        else:
            send_credentials_email(
                to=email, username=username, password=final_password,
                group_name=supervisor_name, totp_secret=totp_secret
            )
        
        # 6. 返回凭据
        credentials = { "totp_secret": totp_secret }
        return True, credentials

    except Exception as e:
        traceback.print_exc()
        return False, str(e)
        
# def _provision_ldap_account(username, full_name, email, supervisor_name, password, user_type):
#     """(最终版) 核心辅助函数，封装所有LDAP账户开通和邮件发送的逻辑"""
#     try:
#         # 1. 为学生生成随机密码，导师则使用传入的密码
#         if user_type == UserType.STUDENT:
#             alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
#             final_password = ''.join(secrets.choice(alphabet) for i in range(10))
#         else: # 导师
#             final_password = password

#         totp_secret = pyotp.random_base32()
        
#         # 2. 准备LDAP信息
#         group_name = ldap_service.format_group_name(supervisor_name)
#         home_dir = f"/home/{username}"
#         next_uid_number = ldap_service.get_next_available_id('uid')
#         next_gid_number = ldap_service.get_next_available_id('gid') 

#         # 3. LDAP操作
#         ldap_service.create_group_if_not_exists(group_name, next_gid_number)
#         ldap_service.create_user(
#             username=username, password=final_password, full_name=full_name,
#             email=email, totp_secret=totp_secret, uid_number=next_uid_number,
#             gid_number=next_gid_number, home_directory=home_dir
#         )
#         ldap_service.add_user_to_group(username, group_name)
        
#         # 4. 自动化配置2FA
#         try:
#             print(f"正在为用户 {username} 在共享 /home 目录配置 2FA...")
#             command = ['sudo', '/home/user/setup_2fa.sh', username, totp_secret, group_name]
#             subprocess.run(command, check=True)
#             print("服务器端 2FA 配置成功。")
#         except Exception as e:
#             print(f"警告：自动配置服务器端 2FA 失败: {e}")

#         # 5. 根据用户类型发送正确的邮件
#         if user_type == UserType.SUPERVISOR:
#             send_supervisor_credentials_email(
#                 to=email, username=username, password=final_password,
#                 group_name=supervisor_name, totp_secret=totp_secret,
#                 web_password=password
#             )
#         else:
#             send_credentials_email(
#                 to=email, username=username, password=final_password,
#                 group_name=supervisor_name, totp_secret=totp_secret
#             )
        
#         # 6. 返回凭据
#         credentials = { "totp_secret": totp_secret }
#         return True, credentials

#     except Exception as e:
#         traceback.print_exc()
#         return False, str(e)

@app.route('/admin/approve/<int:app_id>', methods=['POST'])
@login_required
def approve_application(app_id):
    """主审批路由，采用统一的事务处理"""
    app_to_approve = Application.query.get_or_404(app_id)
    
    try:
        if app_to_approve.user_type == UserType.SUPERVISOR:
            # --- 核心修改：直接解包辅助函数的返回值 ---
            success, data = approve_supervisor_application(app_to_approve)
        
        else: # UserType.STUDENT
            if not app_to_approve.supervisor_approved:
                flash('该学生申请尚未获得导师审批', 'error')
                return redirect(url_for('admin_dashboard'))
            # --- 核心修改：直接解包辅助函数的返回值 ---
            success, data = approve_student_application(app_to_approve)
            
        if not success:
            # 如果辅助函数返回失败，data 就是错误信息字符串
            flash(f"操作失败: {data}", 'error')
            db.session.rollback()
            return redirect(url_for('admin_dashboard'))

        # 如果所有操作都成功了，更新申请状态并进行最终的 commit
        app_to_approve.status = ApplicationStatus.APPROVED
        db.session.commit()
        flash('申请已批准，凭据已通过邮件发送。', 'success')

        # 渲染成功页面给管理员
        totp_secret = data['totp_secret']
        username = app_to_approve.username
        provisioning_uri = pyotp.totp.TOTP(totp_secret).provisioning_uri(
            name=f"{username}@算力平台", issuer_name="SAIDS-USTC"
        )
        img = qrcode.make(provisioning_uri)
        buf = io.BytesIO()
        img.save(buf, 'PNG')
        buf.seek(0)
        qr_code_b64 = base64.b64encode(buf.getvalue()).decode('utf-8')

        return render_template('approve_success.html',
                               username=username, secret=totp_secret,
                               qr_code_b64=qr_code_b64)
    
    except Exception as e:
        db.session.rollback() # 确保任何意外失败都会回滚数据库变更
        flash(f"发生严重错误，操作已回滚: {e}", "error")
        traceback.print_exc()
        return redirect(url_for('admin_dashboard'))

def approve_student_application(application):
    """处理学生申请的审批，只调用核心函数"""
    return _provision_ldap_account(
        username=application.username,
        full_name=application.name,
        email=application.email,
        supervisor_name=application.supervisor_name,
        password=None,  # 传递None，让核心函数内部为学生生成随机密码
        user_type=UserType.STUDENT
    )


def approve_supervisor_application(application):
    """
    (Final Corrected Version) Handles supervisor approval by first committing the
    supervisor record to the database atomically before proceeding with LDAP.
    """
    # Step 1: Check if the supervisor record already exists in the database.
    existing_supervisor = Supervisor.query.filter(
        db.or_(Supervisor.email == application.email, Supervisor.username == application.username)
    ).first()

    if not existing_supervisor:
        # If the supervisor does not exist, create and COMMIT the record now.
        print(f"Supervisor '{application.username}' not found in DB. Creating and committing new record...")
        
        new_supervisor = Supervisor(
            name=application.name,
            email=application.email,
            username=application.username,
            department=application.department,
            receive_monthly_report=True,
            receive_quarterly_report=True
        )
        new_supervisor.set_password(application.password)
        db.session.add(new_supervisor)
        
        try:
            db.session.commit()
            print(f"New supervisor '{application.username}' record has been successfully committed to the database.")
        except Exception as e:
            db.session.rollback()
            print(f"Failed to commit new supervisor record to DB: {e}")
            # Raise the exception so the main approval route knows it failed
            raise Exception(f"Database operation failed: {e}")
    else:
        print(f"Supervisor '{application.username}' already exists in DB. Skipping DB record creation.")

    # Step 2: Now that the database part is secure, proceed with the LDAP provisioning.
    # The return value of this will be handled by the main approve_application route.
    return _provision_ldap_account(
        username=application.username,
        full_name=application.name,
        email=application.email,
        supervisor_name=application.name,
        password=application.password,
        user_type=UserType.SUPERVISOR
    )

def supervisor_required(f):
    """装饰器：确保只有登录的导师可以访问"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'supervisor_username' not in session or session.get('user_type') != 'supervisor':
            flash('请先以导师身份登录', 'error')
            return redirect(url_for('index'))
        
        # 验证导师是否存在
        supervisor = Supervisor.query.filter_by(username=session['supervisor_username']).first()
        if not supervisor:
            session.clear()  # 清除所有session
            flash('导师账户不存在，请重新登录', 'error')
            return redirect(url_for('index'))
        
        return f(*args, **kwargs)
    return decorated_function

def student_required(f):
    """装饰器：确保只有登录的学生可以访问"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'student_username' not in session or session.get('user_type') != 'student':
            flash('请先以学生身份登录', 'error')
            return redirect(url_for('index'))
        return f(*args, **kwargs)
    return decorated_function

@app.route('/')
def index():
    """主页显示注册和登录表单"""
    # 添加一个参数来强制显示主页
    force_index = request.args.get('force') == 'true'
    
    if not force_index:
        # 如果已登录，重定向到相应的仪表盘
        if 'supervisor_username' in session and session.get('user_type') == 'supervisor':
            return redirect(url_for('supervisor_dashboard'))
        elif 'student_username' in session and session.get('user_type') == 'student':
            return redirect(url_for('student_dashboard'))
        elif current_user.is_authenticated:
            return redirect(url_for('admin_dashboard'))
    
    try:
        # 安全地获取导师列表，如果表不存在则返回空列表
        supervisors = Supervisor.query.all() if 'supervisor' in db.inspect(db.engine).get_table_names() else []
    except Exception as e:
        print(f"Error fetching supervisors: {e}")
        supervisors = []
    
    return render_template('index.html', supervisors=supervisors)

@app.route('/api/register', methods=['POST'])
def api_register():
    data = request.get_json()
    user_type = data.get('user_type', 'student')
    
    if user_type == 'student':
        return handle_student_registration(data)
    elif user_type == 'supervisor':
        return handle_supervisor_registration(data)
    else:
        return jsonify({'error': '无效的用户类型'}), 400

def handle_student_registration(data):
    name = data.get('name')
    email = data.get('email')
    student_id = data.get('student_id')
    supervisor_email = data.get('supervisor_email')
    supervisor_name = data.get('supervisor')
    password = data.get('password')
    username = data.get('username')

    if not all([name, email, student_id, supervisor_email, supervisor_name, password, username]):
        return jsonify({'error': '所有字段均为必填项'}), 400

    # 检查重复 (保持不变)
    if Application.query.filter_by(email=email).first():
        return jsonify({'error': '该邮箱已被注册'}), 400
    if Application.query.filter_by(username=username).first():
        return jsonify({'error': '该用户名已被注册'}), 400
    if Application.query.filter_by(student_id=student_id).first():
        return jsonify({'error': '该学号已被注册'}), 400

    # --- 核心修改：检查导师是否存在 ---
    supervisor_exists = Supervisor.query.filter_by(email=supervisor_email).first()

    # 先创建申请记录
    new_application = Application(
        name=name,
        email=email,
        student_id=student_id,
        supervisor_email=supervisor_email,
        supervisor_name=supervisor_name,
        password=password,
        username=username,
        user_type=UserType.STUDENT,
        # 导师审批状态默认为 False
        supervisor_approved=False 
    )
    db.session.add(new_application)
    db.session.commit()

    # 根据导师是否存在，发送不同的邮件
    if supervisor_exists:
        # 导师已存在，发送登录审批提醒邮件
        send_supervisor_notification_email(supervisor_name, supervisor_email, name, student_id)
        return jsonify({'message': '学生申请提交成功！已发送邮件提醒您的导师登录平台进行审批。'}), 200
    else:
        # 导师不存在，发送邀请注册邮件
        send_supervisor_invitation_email(supervisor_name, supervisor_email, name, student_id)
        return jsonify({'message': '学生申请提交成功！您的导师尚未注册，系统已发送邀请邮件，请提醒导师先注册平台账户。'}), 200

def handle_supervisor_registration(data):
    name = data.get('name')
    email = data.get('email')
    department = data.get('department')
    password = data.get('password')
    username = data.get('username')
    
    if not all([name, email, department,password, username]):
        return jsonify({'error': '所有字段均为必填项'}), 400

    # 检查重复
    if Application.query.filter_by(email=email).first():
        return jsonify({'error': '该邮箱已被注册'}), 400
    if Application.query.filter_by(username=username).first():
        return jsonify({'error': '该用户名已被注册'}), 400

    new_application = Application(
        name=name,
        email=email,
        supervisor_name=name,  # 导师的supervisor_name就是自己
        password=password,
        username=username,
        user_type=UserType.SUPERVISOR,
        department=department,

    )
    
    db.session.add(new_application)
    db.session.commit()
    
    return jsonify({'message': '导师申请提交成功！请等待管理员审核。'}), 200

@app.route('/admin/dashboard')
@login_required 
def admin_dashboard():
    # 获取筛选参数
    supervisor_filter = request.args.get('supervisor', '')
    status_filter = request.args.get('status', 'PENDING')
    user_type_filter = request.args.get('user_type', 'ALL')
    
    # 构建查询
    query = Application.query
    
    if supervisor_filter:
        query = query.filter(Application.supervisor_name.contains(supervisor_filter))
    
    if status_filter and status_filter != 'ALL':
        query = query.filter(Application.status == ApplicationStatus[status_filter])
    
    if user_type_filter and user_type_filter != 'ALL':
        query = query.filter(Application.user_type == UserType[user_type_filter])
    
    # 按优先级排序：导师申请在前，然后是已被导师审批的学生申请
    applications = query.order_by(
        Application.user_type.desc(),  # 导师申请在前
        Application.supervisor_approved.desc(),  # 已审批的学生在前
        Application.submitted_at.desc()
    ).all()
    
    # 获取所有导师列表用于筛选下拉框
    supervisors = db.session.query(Application.supervisor_name).distinct().all()
    supervisor_list = [s[0] for s in supervisors if s[0]]  # 过滤掉None值
    
    # 修正后的统计信息，字段名与HTML模板匹配
    stats = {
        'total_applications': Application.query.count(),
        'pending_applications': Application.query.filter_by(status=ApplicationStatus.PENDING).count(),
        'approved_applications': Application.query.filter_by(status=ApplicationStatus.APPROVED).count(),
        'rejected_applications': Application.query.filter_by(status=ApplicationStatus.REJECTED).count(),
        'student_applications': Application.query.filter_by(user_type=UserType.STUDENT).count(),
        'supervisor_applications': Application.query.filter_by(user_type=UserType.SUPERVISOR).count(),
        'waiting_supervisor_approval': Application.query.filter_by(
            user_type=UserType.STUDENT,
            supervisor_approved=False,
            status=ApplicationStatus.PENDING
        ).count(),
        # 保留原有的统计项目（用于其他地方）
        'total': Application.query.count(),
        'pending': Application.query.filter_by(status=ApplicationStatus.PENDING).count(),
        'approved': Application.query.filter_by(status=ApplicationStatus.APPROVED).count(),
        'rejected': Application.query.filter_by(status=ApplicationStatus.REJECTED).count(),
        'supervisor_pending': Application.query.filter_by(
            user_type=UserType.SUPERVISOR, 
            status=ApplicationStatus.PENDING
        ).count(),
        'student_pending': Application.query.filter_by(
            user_type=UserType.STUDENT, 
            status=ApplicationStatus.PENDING,
            supervisor_approved=True
        ).count()
    }
    
    return render_template('admin_dashboard.html', 
                         applications=applications,
                         supervisor_list=supervisor_list,
                         current_supervisor=supervisor_filter,
                         current_status=status_filter,
                         current_user_type=user_type_filter,
                         stats=stats)

@app.route('/admin/users')
@login_required
def admin_users():
    """管理员查看所有已批准的用户"""
    approved_applications = Application.query.filter_by(status=ApplicationStatus.APPROVED).all()
    
    # 按导师分组
    users_by_supervisor = {}
    for app in approved_applications:
        supervisor = app.supervisor_name
        if supervisor not in users_by_supervisor:
            users_by_supervisor[supervisor] = []
        users_by_supervisor[supervisor].append({
            'username': app.username,
            'name': app.name,
            'email': app.email,
            'submitted_at': app.submitted_at,
            'group_name': ldap_service.format_group_name(supervisor) if supervisor else 'N/A'
        })
    
    return render_template('admin_users.html', users_by_supervisor=users_by_supervisor)

@app.cli.command("create-ldap-student")
@click.argument("username")
@click.argument("fullname")
@click.argument("email")
@click.argument("supervisor_name")
def create_ldap_student(username, fullname, email,supervisor_name):
    """
    直接创建并配置一个完整的LDAP学生账户，用于测试。
    用法: flask create-ldap-student <用户名> <全名> <邮箱> <导师名>
    """
    print(f"--- 正在直接为'{fullname}'创建LDAP学生账户 ---")
    
    # 直接调用我们的核心供应函数
    success, error_msg = _provision_ldap_account(
        username=username,
        full_name=fullname,
        email=email,
        supervisor_name=supervisor_name,
        password='Wangtf123',
        user_type=UserType.STUDENT
    )
    
    if success:
        print("\n--- ✅ 操作成功 ---")
        print(f"学生账户 '{username}' 已成功创建，并已发送凭据邮件至 {email}。")
    else:
        print(f"\n--- ❌ 操作失败 ---")
        print(f"错误信息: {error_msg}")

@app.route('/admin/reject/<int:app_id>', methods=['POST'])
@login_required
def reject_application(app_id):
    """管理员拒绝申请"""
    app_to_reject = Application.query.get_or_404(app_id)
    app_to_reject.status = ApplicationStatus.REJECTED
    db.session.commit()
    flash('申请已拒绝', 'success')
    return redirect(url_for('admin_dashboard'))
 
@app.route('/admin/login', methods=['GET', 'POST'])
def admin_login():
    """管理员登录页面"""
    # 如果已经登录，直接跳转到仪表盘
    if current_user.is_authenticated:
        return redirect(url_for('admin_dashboard'))
    
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        if not username or not password:
            flash('请输入用户名和密码', 'error')
            return redirect(url_for('admin_login'))
        
        user = AdminUser.query.filter_by(username=username).first()
        
        if user and user.check_password(password):
            # 在开发环境中不使用 remember
            remember = not app.config.get('DEBUG', False)  # 如果是 DEBUG 模式，不记住登录
            login_user(user, remember=remember)
            
            # 获取next参数，如果没有则默认跳转到仪表盘
            next_page = request.args.get('next')
            if not next_page or not next_page.startswith('/'):
                next_page = url_for('admin_dashboard')
            
            flash('登录成功！', 'success')
            return redirect(next_page)
        else:
            flash('用户名或密码错误', 'error')
            return redirect(url_for('admin_login'))
    
    # GET请求，显示登录页面
    return render_template('admin_login.html')

@app.route('/admin/logout')
@login_required
def admin_logout():
    """管理员登出"""
    logout_user()
    flash('您已成功登出', 'success')
    return redirect(url_for('index'))

@app.route('/api/login', methods=['POST'])
def api_login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    # 打印请求的初始信息
    print(f"\n--- [API Login] 收到登录请求，用户: '{username}' ---")

    if not username or not password:
        print("[API Login] 失败：用户名或密码为空。")
        return jsonify({'error': '用户名和密码不能为空'}), 400
    
    # 步骤 1: 检查这个用户名是否在我们系统的导师数据库中
    print(f"[API Login] 步骤1: 在 Supervisor 数据库中查找用户 '{username}'...")
    supervisor = Supervisor.query.filter_by(username=username).first()
    
    if supervisor:
        print(f"[API Login] 结果: 在 Supervisor 数据库中找到了用户 '{username}'。")
        # 如果是导师，则只在这里进行验证
        
        print(f"[API Login] 步骤2: 正在验证 Supervisor 的 Web 平台密码...")
        is_password_correct = supervisor.check_password(password)
        print(f"[API Login] 结果: 密码是否正确? -> {is_password_correct}")

        if is_password_correct:
            # 导师密码正确，登录成功
            print(f"[API Login] 结论: 导师 '{username}' 登录成功。")
            session.clear()
            session['supervisor_username'] = username
            session['user_type'] = 'supervisor'
            session.permanent = True
            return jsonify({
                'message': '导师登录成功', 
                'user_type': 'supervisor',
                'redirect': url_for('supervisor_dashboard')
            }), 200
        else:
            # 导师密码错误，直接返回失败
            print(f"[API Login] 结论: 导师 '{username}' 密码错误，登录失败。")
            return jsonify({'error': '用户名或密码错误'}), 401

    # 步骤 2: 如果不是导师，再尝试作为学生通过LDAP验证
    else:
        print(f"[API Login] 结果: 在 Supervisor 数据库中未找到用户 '{username}'。继续进行LDAP学生认证...")
        
        if current_app.config.get('LDAP_ENABLED', False):
            print(f"[API Login] 步骤2: 正在通过 LDAP 验证用户 '{username}'...")
            is_authenticated = ldap_service.authenticate_user(username, password)
            print(f"[API Login] 结果: LDAP认证是否成功? -> {is_authenticated}")

            if is_authenticated:
                # 学生LDAP验证成功
                print(f"[API Login] 结论: LDAP用户 '{username}' 登录成功，身份为学生。")
                session.clear()
                session['student_username'] = username
                session['user_type'] = 'student'
                session.permanent = True
                return jsonify({
                    'message': '学生登录成功', 
                    'user_type': 'student',
                    'redirect': url_for('student_dashboard')
                }), 200

    # 所有情况都验证失败
    print(f"[API Login] 结论: 所有认证方式均失败。")
    return jsonify({'error': '用户名或密码错误'}), 401


@app.cli.command("create-admin")
@click.argument("username")
@click.argument("password")
def create_admin(username, password):
    """
    创建一个新的管理员用户。
    用法: flask create-admin <用户名> <密码>
    """
    # 检查用户名是否已存在
    if AdminUser.query.filter_by(username=username).first():
        print(f"错误: 管理员用户 '{username}' 已存在。")
        return

    # 创建新的管理员实例
    new_admin = AdminUser(username=username)
    new_admin.set_password(password) # 使用您在模型中定义的 set_password 方法来哈希密码

    # 将新用户添加到数据库
    db.session.add(new_admin)
    db.session.commit()

    print(f"成功创建管理员用户: '{username}'。")

@app.route('/student/dashboard')
@student_required  # 使用装饰器保护路由
def student_dashboard():
    # 从 session 中获取用户名
    username = session.get('student_username', '同学')
    return render_template('student_dashboard.html', username=username)

@app.route('/student/logout')
def student_logout():
    session.clear()  # 清除所有session
    flash('您已成功登出', 'success')
    return redirect(url_for('index'))

@app.route('/supervisor/approve/<int:app_id>')
def supervisor_approve_student(app_id):
    """导师审批学生申请的页面（邮件链接）"""
    application = Application.query.get_or_404(app_id)
    
    if application.user_type != UserType.STUDENT:
        return "无效的申请", 400
    
    return render_template('supervisor_approve.html', application=application)

@app.route('/supervisor/approve/<int:app_id>/confirm', methods=['POST'])
def supervisor_confirm_student(app_id):
    """导师确认审批学生（邮件链接）"""
    application = Application.query.get_or_404(app_id)
    
    if application.user_type != UserType.STUDENT:
        return "无效的申请", 400
    
    action = request.form.get('action')
    
    if action == 'approve':
        application.supervisor_approved = True
        db.session.commit()
        send_student_supervisor_approval_email(application)
        return render_template('supervisor_approve_success.html', 
                             student_name=application.name,
                             action='approved')
    elif action == 'reject':
        application.status = ApplicationStatus.REJECTED
        db.session.commit()
        return render_template('supervisor_approve_success.html', 
                             student_name=application.name,
                             action='rejected')
    
    return "无效操作", 400

@app.route('/supervisor/dashboard')
@supervisor_required  # 使用装饰器保护路由
def supervisor_dashboard():
    """导师专用仪表盘"""
    supervisor_username = session['supervisor_username']
    supervisor = Supervisor.query.filter_by(username=supervisor_username).first()
    
    # 获取待审批的学生申请（指定了该导师的）
    pending_student_applications = Application.query.filter_by(
        user_type=UserType.STUDENT,
        supervisor_name=supervisor.name,
        supervisor_approved=False,
        status=ApplicationStatus.PENDING
    ).order_by(Application.submitted_at.desc()).all()
    
    # 获取已审批的学生列表
    approved_students = Application.query.filter_by(
        user_type=UserType.STUDENT,
        supervisor_name=supervisor.name,
        status=ApplicationStatus.APPROVED
    ).all()
    
    # 检查是否有成功消息参数
    success_message = request.args.get('success')
    
    return render_template('supervisor_dashboard.html',
                         supervisor=supervisor,
                         pending_applications=pending_student_applications,
                         approved_students=approved_students,
                         success_message=success_message)

@app.route('/supervisor/approve_student/<int:app_id>', methods=['POST'])
@supervisor_required  # 使用装饰器保护路由
def supervisor_approve_student_dashboard(app_id):
    """导师在自己界面审批学生"""
    supervisor_username = session['supervisor_username']
    supervisor = Supervisor.query.filter_by(username=supervisor_username).first()
    
    application = Application.query.get_or_404(app_id)
    
    if application.supervisor_name != supervisor.name:
        flash('无权限操作此申请', 'error')
        return redirect(url_for('supervisor_dashboard'))
    
    action = request.form.get('action')
    
    if action == 'approve':
        application.supervisor_approved = True
        db.session.commit()
        
        # 发送通知邮件给学生
        send_student_supervisor_approval_email(application)
        
        # 添加成功参数到URL
        return redirect(url_for('supervisor_dashboard', success='approve'))
        
    elif action == 'reject':
        application.status = ApplicationStatus.REJECTED
        db.session.commit()
        
        # 添加成功参数到URL
        return redirect(url_for('supervisor_dashboard', success='reject'))
    
    return redirect(url_for('supervisor_dashboard'))

@app.route('/supervisor/settings', methods=['GET', 'POST'])
@supervisor_required  # 使用装饰器保护路由
def supervisor_settings():
    """导师设置页面（保存报告偏好）"""
    supervisor_username = session['supervisor_username']
    supervisor = Supervisor.query.filter_by(username=supervisor_username).first()
    
    if request.method == 'POST':
        # 更新报告接收设置
        supervisor.receive_monthly_report = 'monthly_report' in request.form
        supervisor.receive_quarterly_report = 'quarterly_report' in request.form
        db.session.commit()
        
        # 发送确认邮件（可选）
        send_settings_update_email(supervisor)
        
        # 重定向到仪表盘并显示成功消息
        return redirect(url_for('supervisor_dashboard', success='settings'))
    
    # GET请求时返回设置页面
    return render_template('supervisor_settings.html', supervisor=supervisor)

@app.route('/supervisor/logout')
def supervisor_logout():
    """导师登出"""
    session.clear()  # 清除所有session
    flash('您已成功登出', 'success')
    return redirect(url_for('index'))

def send_student_supervisor_approval_email(application):
    """通知学生导师已审批"""
    try:
        msg = Message(
            subject="您的导师已审批您的算力平台申请",
            sender=current_app.config['MAIL_DEFAULT_SENDER'],
            recipients=[application.email]
        )
        
        msg.body = f"""
            {application.name} 同学：

            您好！

            您的导师 {application.supervisor_name} 已审批您的算力平台账户申请。

            您的申请现已提交给管理员进行最终审核，请耐心等待。
            管理员审核通过后，您将收到账户激活邮件。

            此致
            人工智能与数据科学学院算力中心
        """
        mail.send(msg)
        print(f"学生导师审批通知邮件已发送至 {application.email}")
    except Exception as e:
        print(f"发送学生导师审批通知邮件失败: {e}")

def send_settings_update_email(supervisor):
    """发送设置更新确认邮件给导师"""
    try:
        msg = Message(
            subject="您的报告接收设置已更新",
            sender=current_app.config['MAIL_DEFAULT_SENDER'],
            recipients=[supervisor.email]
        )
        
        settings_text = []
        if supervisor.receive_monthly_report:
            settings_text.append("- 月度资源使用报告：已开启")
        else:
            settings_text.append("- 月度资源使用报告：已关闭")
            
        if supervisor.receive_quarterly_report:
            settings_text.append("- 季度资源使用报告：已开启")
        else:
            settings_text.append("- 季度资源使用报告：已关闭")
        
        msg.body = f"""
                    尊敬的 {supervisor.name} 导师：

                    您好！

                    您的报告接收设置已成功更新：

                    {chr(10).join(settings_text)}

                    如需再次修改，请登录导师管理界面进行设置。

                    此致
                    人工智能与数据科学学院算力中心
                    """
        mail.send(msg)
        print(f"设置更新邮件已发送至 {supervisor.email}")
    except Exception as e:
        print(f"发送设置更新邮件失败: {e}")


@app.cli.command("create-test-supervisor")
@click.argument("username")
@click.argument("password")
@click.argument("name")
@click.argument("email")
@click.argument("department")
def create_test_supervisor(username, password, name, email, department):
    """(已修正) 创建测试导师账户，并确保事务的完整性"""
    # 检查是否已存在
    if Supervisor.query.filter_by(username=username).first() or \
       Supervisor.query.filter_by(email=email).first():
        print(f"错误: 用户名 '{username}' 或邮箱 '{email}' 已被使用。")
        return
    
    # 1. 创建新导师对象，但不提交
    new_supervisor = Supervisor(
        username=username, name=name, email=email, department=department,
        receive_monthly_report=True, receive_quarterly_report=True
    )
    new_supervisor.set_password(password)
    db.session.add(new_supervisor)

    # 2. 调用核心函数开通LDAP账户
    print("正在为测试导师开通LDAP账户...")
    success, data = _provision_ldap_account(
        username=username, full_name=name, email=email,
        supervisor_name=name, password=password,
        user_type=UserType.SUPERVISOR
    )

    # 3. 只有在所有操作都成功后，才最终提交数据库
    if success:
        try:
            db.session.commit()
            print("\n--- ✅ 操作成功 ---")
            print(f"成功创建测试导师账户（数据库与LDAP）:")
            print(f"  用户名: {username}")
            print(f"  Web密码: {password}")
            print(f"  姓名: {name}")
            print(f"  邮箱: {email}")
            print(f"\n现在可以使用此账户登录导师界面了！")
        except Exception as e:
            print(f"\n--- ❌ 数据库提交失败 ---")
            print(f"错误: {e}")
            db.session.rollback()
    else:
        print(f"\n--- ❌ LDAP或邮件操作失败 ---")
        print(f"错误信息: {data}")
        db.session.rollback() # 回滚未提交的 new_supervisor

@app.before_request
def check_session_validity():
    """在每个请求前检查session的有效性"""
    # 跳过静态文件和公开路由
    if request.endpoint in ['static', 'index', 'api_login', 'api_register', 
                           'admin_login', 'supervisor_approve_student', 
                           'supervisor_confirm_student']:
        return
    
    # 检查session中的用户类型是否与访问的路由匹配
    if request.endpoint and request.endpoint.startswith('supervisor_'):
        if 'supervisor_username' not in session or session.get('user_type') != 'supervisor':
            session.clear()
            flash('请先以导师身份登录', 'error')
            return redirect(url_for('index'))
    
    elif request.endpoint and request.endpoint.startswith('student_'):
        if 'student_username' not in session or session.get('user_type') != 'student':
            session.clear()
            flash('请先以学生身份登录', 'error')
            return redirect(url_for('index'))

@app.route('/clear-all')
def clear_all_sessions():
    """临时路由：清除所有会话"""
    session.clear()
    if current_user.is_authenticated:
        logout_user()
    flash('所有会话已清除', 'success')
    return redirect(url_for('index'))

@app.errorhandler(404)
def not_found_error(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return render_template('500.html'), 500

@app.errorhandler(401)
def unauthorized_error(error):
    flash('请先登录以访问此页面', 'error')
    return redirect(url_for('index'))



if __name__ == '__main__':
    # 在第一次运行前，请在终端执行以下命令：
    # flask db init
    # flask db migrate -m "Initial migration"
    # flask db upgrade
    # flask create-admin admin admin123  # 创建管理员账户
    # flask create-test-supervisor test_supervisor password123 张教授 zhang@example.com 计算机学院
    with app.app_context():
        db.create_all()  # 确保所有表都被创建
    app.run(host='0.0.0.0', port=5000, debug=True)


import commands